"""
Event capability mixin for adding event functionality to classes.

This module provides a mixin class that can be inherited by training classes
to add comprehensive event emission capabilities. The mixin follows the
composition pattern, allowing existing classes to gain event functionality
without major architectural changes.

Core Class:
- EventCapableMixin: Complete event emission capability with unified configuration

Key Features:
- Unified configuration interface using EventConfig
- Template-based configuration ("basic", "continual", "research", etc.)
- Automatic event system setup with factory pattern
- Graceful fallback to null emitter on configuration failure
- Built-in event statistics and monitoring
- Support for dynamic enable/disable of events
- Backward compatibility with legacy configuration formats

Example:
    >>> from learners.events.mixins import EventCapableMixin
    >>>
    >>> class MyLearner(EventCapableMixin):
    ...     def __init__(self):
    ...         super().__init__()
    ...         self.configure_events("continual")  # Template-based config
    ...
    ...     def train_epoch(self, epoch):
    ...         self.emit_event(EventType.ON_TRAIN_EPOCH_START, epoch=epoch)
    ...         # training logic
    ...         self.emit_event(EventType.ON_TRAIN_EPOCH_END, epoch=epoch)
    ...
    ...     def train_task(self, task_id):
    ...         self.emit_event(EventType.ON_TASK_START, task_id=task_id)
    ...         # task training
    ...         self.emit_event(EventType.ON_AFTER_TASK_TRAINING, task_id=task_id)

Configuration Examples:
    >>> # Template-based (recommended)
    >>> self.configure_events("continual")
    >>>
    >>> # Direct EventConfig object
    >>> config = EventConfig.from_template("research")
    >>> self.configure_events(config)
    >>>
    >>> # From configuration file
    >>> self.configure_events("config/my_events.yaml")
    >>>
    >>> # Legacy dict format (backward compatibility)
    >>> self.configure_events({"enable_events": True, "event_mode": "basic"})

Design Patterns:
- Mixin Pattern: Composable functionality through single inheritance
- Factory Pattern: EventSystemFactory creates appropriate emitter instances
- Null Object Pattern: NullEventEmitter provides safe no-op behavior
- Unified Interface: Single mixin covers all event emission needs

Architecture:
    The mixin encapsulates the complexity of event system configuration and
    provides a clean interface for event emission. It integrates with the
    unified EventConfig system and handles error conditions gracefully while
    providing monitoring capabilities for debugging.
"""

from typing import Optional, Dict, Any, Union
import logging
from pathlib import Path

from learners.interfaces.event import EventType
from learners.events.emitters import EventEmitter, NullEventEmitter
from learners.events.factory import EventSystemFactory
from learners.events.config import EventConfig, configure_events


logger = logging.getLogger(__name__)


class EventCapableMixin:
    """
    Base mixin to add event emission capability to any class.

    This mixin provides the foundational event system integration, including
    configuration management, event emission, and monitoring capabilities.
    Classes that inherit from this mixin gain the ability to emit events
    throughout their lifecycle.

    Key Features:
    - Unified configuration interface using EventConfig
    - Template-based configuration ("basic", "continual", "research", etc.)
    - Automatic fallback to null emitter on errors
    - Event statistics tracking
    - Dynamic enable/disable functionality
    - Backward compatibility with legacy configuration formats

    Attributes:
        _event_emitter: The active event emitter instance
        _event_enabled: Whether event emission is currently enabled

    Example:
        >>> class MyClass(EventCapableMixin):
        ...     def __init__(self):
        ...         super().__init__()
        ...         self.configure_events("continual")
        ...
        ...     def do_work(self):
        ...         self.emit_event(EventType.ON_TRAIN_START)
        ...         # work logic
        ...         self.emit_event(EventType.ON_TRAIN_END)
    """

    def __init__(self, *args, **kwargs):
        """
        Initialize the event-capable mixin.

        Sets up the event system in a disabled state. Events must be
        explicitly configured using configure_events() before emission
        will occur.
        """
        super().__init__(*args, **kwargs)
        self._event_emitter: EventEmitter = NullEventEmitter()
        self._event_enabled = False

    def configure_events(
        self,
        config: Union[EventConfig, str, Dict[str, Any], Path, None] = None,
        **overrides,
    ) -> None:
        """
        Configure the event system with unified interface.

        Args:
            config: Configuration input:
                  - EventConfig: Direct configuration object (most efficient)
                  - str: Template name ("continual", "basic", etc.) or file path
                  - Dict: Configuration parameters
                  - None: Use default "basic" template
            **overrides: Direct configuration overrides (enabled=True, etc.)

        Example:
            >>> # Template-based (most common)
            >>> self.configure_events("continual")
            >>>
            >>> # From file
            >>> self.configure_events("config/events.yaml")
            >>>
            >>> # Direct EventConfig (most efficient)
            >>> config = configure_events("continual")
            >>> self.configure_events(config)
        """
        try:
            # Convert input to EventConfig using the unified configure_events function
            if isinstance(config, EventConfig):
                # Already an EventConfig, just apply overrides if any
                if overrides:
                    config_dict = config.to_dict()
                    config_dict.update(overrides)
                    event_config = EventConfig.from_dict(config_dict)
                else:
                    event_config = config
            elif isinstance(config, str) and Path(config).exists():
                # File path
                event_config = configure_events(config_file=config, **overrides)
            elif isinstance(config, str):
                # Template name
                event_config = configure_events(template=config, **overrides)
            elif isinstance(config, dict):
                # Dictionary configuration
                event_config = configure_events(**config, **overrides)
            elif config is None:
                # Default configuration
                event_config = configure_events(template="basic", **overrides)
            else:
                raise ValueError(f"Unsupported configuration type: {type(config)}")

            if not event_config.enabled:
                logger.info("Events disabled by configuration")
                self._event_emitter = NullEventEmitter()
                self._event_enabled = False
                return

            # Create event emitter using factory
            self._event_emitter = EventSystemFactory.create_event_emitter(
                {"event_config": event_config}, self
            )
            self._event_enabled = event_config.enabled

            logger.info(
                f"Event system configured for {self.__class__.__name__} "
                f"with {len(event_config.handlers)} handlers"
            )

        except Exception as e:
            logger.warning(f"Failed to configure event system: {e}")
            self._event_emitter = NullEventEmitter()
            self._event_enabled = False

    def emit_event(self, event: EventType, **kwargs) -> Optional[Dict[str, Any]]:
        """
        Emit an event with the given type and context data.

        This is the core method for event emission. It delegates to the
        configured event emitter and handles errors gracefully.

        Args:
            event: Type of event to emit
            **kwargs: Additional context data to include with the event

        Returns:
            Dict of handler results if any, None otherwise

        Example:
            >>> self.emit_event(
            ...     EventType.ON_TRAIN_EPOCH_START,
            ...     epoch=5,
            ...     learning_rate=0.001
            ... )
        """
        if not self._event_enabled:
            return None

        try:
            return self._event_emitter.emit_event(event, **kwargs)
        except Exception as e:
            logger.warning(f"Event emission failed for {event.value}: {e}")
            return None

    def is_events_enabled(self) -> bool:
        """Check if events are enabled."""
        return self._event_enabled

    def get_event_manager(self):
        """
        Get the underlying event manager.

        Returns:
            EventManager instance if available, None otherwise
        """
        if hasattr(self._event_emitter, "_event_manager"):
            return self._event_emitter._event_manager
        return None

    def disable_events(self) -> None:
        """
        Disable event emission for this instance.

        Replaces the current emitter with a null emitter to prevent
        any event processing overhead.
        """
        self._event_emitter = NullEventEmitter()
        self._event_enabled = False
        logger.info(f"Events disabled for {self.__class__.__name__}")
